<?php
defined('ENV') || exit('illegal Access! @110');

/**
 * Class DB
 * MySQL数据库链接类
 * 分两部分: 1. 数据库链接 2. sql组装
 */
class DB
{
    /**
     * 数据库链接参数:
     */
    public $dbType = 'read';// read 或 write

	public $_host=''; //数据库所在主机名
    public $_database = '';//当前数据库名
    public $_tablename = '';//当前表的表名
    public $_dt = '';  //database.tablename
    public $connectKey = ''; //把已建立的链接存放在$GLOBALS['DB_LINKS']中, 键名为此变量
    public $connectError = ''; //连接失败原因

    public $isRelease = 0; //查询完成后是否释放
	public $insertId = 0;
	public $affectRows = 0;
	public $custom = FALSE; //是否是直接查询SQL语句, 例如: query('selct * from ....')

	public $rs; //数据库查询数据集
	public $data = array(); //查询的主数据
	public $relData = array(); //存放最近一次关联的数据, 暂未使用, 准备为关联多层数据用

    public static $sqls = array();
    public static $currentSql = '';

    /**
     * SQL语句组装参数
     */
    public $fields = '*';
    public $arrWhere = [];
    public $order = '';
    public $arrOrder = [];
    public $limit = '';
    public $groupBy = '';
    public $having = '';
    public $arrUpdate = [];
    public $extra = '';

    public $joinField = [];
    public $joinTable = [];
    public $joinOn = [];
    public $joinAnd = []; //表联结时的筛选条件

    public $raw = ['UUID()', 'RAND()', 'NOW()', 'NULL']; //需要保持原样, 不加引号组装进SQL的字符串
    //mysql保留函数 https://dev.mysql.com/doc/refman/8.0/en/func-op-summary-ref.html
    public $keywords = [
         'AVG', 'CONCAT', 'CONCAT_WS', 'COUNT',  'CURDATE', 'CURRENT_DATE',
        'CURRENT_TIME', 'CURRENT_TIMESTAMP','CURTIME', 'DATE', 'DATE_ADD', 'DATE_FORMAT', 'DATE_SUB',
        'DAY', 'DAYNAME', 'DAYOFMONTH', 'DAYOFWEEK', 'DAYOFYEAR', 'DIV',
         'FLOOR','FROM_DAYS', 'FROM_UNIXTIME',
        'GROUP_CONCAT', 'HOUR', 'IFNULL', 'LAST_DAY', 'LAST_INSERT_ID',
        'MAX','MIN', 'MOD', 'MONTH', 'MONTHNAME','NOW','POWER', 'RAND', 'ROUND',
        'STRCMP', 'SUBDATE', 'SUBSTR', 'SUBSTRING',  'SUBTIME', 'SUM', 'SYSDATE', 'TIME', 'TIME_FORMAT', 'TIME_TO_SEC', 'TIMESTAMP',
        'TIMESTAMPADD', 'TIMESTAMPDIFF', 'TO_BASE64', 'TO_DAYS', 'TO_SECONDS', 'TRIM',
        'UNIX_TIMESTAMP', 'UPPER', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'UUID', 'UUID_SHORT',
         'WEEK', 'WEEKDAY', 'WEEKOFYEAR', 'YEAR', 'YEARWEEK',
    ];

	public $message = [];

	//构造函数
    public function __construct($host='', $database='', $tablename='', $isRelease=0)
    {
        $this->_host = $host;//主机名
        $this->_database = $database;//数据库名
        $this->_tablename = $tablename;//表名
        $this->_dt = $database.'.'.$tablename;//数据库名.表名 sql语句中from用了这个值
        $this->isRelease = $isRelease;
    }

	/**
	 * desc 获取链接实例
	 * @param string  $vName 虚拟表名, 对应DBConfig中$TableInfo的键名
	 * @param int $isRelease 执行完sql语句后是否关闭连接，大并发下需要关闭连接
	 * @return DB|null
     * @throws Exception
	 */
	public static function link($vName, $isRelease=0)
	{
		$tableinfo 	= self::getDBInfo($vName);
		$host = $tableinfo['vhost'];//host vname
		$database = $tableinfo['database'];//database name
		$tablename = $tableinfo['table'];//table name

        return new self($host, $database, $tablename, $isRelease);
	}

	//创建一个新的mysql链接
	public function newConnect($host, $username, $password, $dbname, $port, $charset='utf8mb4')
    {
        $mysqli = mysqli_init(); //初始化mysqli
        $mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 3); //超时3s
        $mysqli->options(MYSQLI_INIT_COMMAND, "set names {$charset}");

        //连接错误时报错信息会是乱码
        if ($mysqli->real_connect($host, $username, $password, $dbname, $port)) {
            $this->connectError = '';
            return $mysqli;
        } else {
            $this->connectError = $mysqli->connect_error;
            //$connectError[$host] = mysqli_connect_error();
            return false;
        }
    }

	//如果主机没变,并且已经存在MYSQL连接,就不再创建新的连接
	//如果主机改变,就再生成一个实例创建一个连接
    //$type == 'write'或'read'
	public function getConnect($type)
	{
        $this->dbType = $type;

        $this->connectKey = $this->_host.'::'.$this->dbType; //例如 default::read

		//已经存在链接, 直接返回链接名
        if (!empty($GLOBALS['DB_LINKS'][$this->connectKey])) {
        	return $this; // 方便调用事务等其他方法
        }

        //随机选取一个可用的数据库连接(区分读写)
        DBConfig::init();
        $hosts = DBConfig::$hosts[$this->_host][$type];

        $isConnected = FALSE;
        $connectCounter = 5; //最多5次重连
        $connectError = array();
        while (!$isConnected && $connectCounter > 0) {
        	$randKey = array_rand($hosts); //随机选取一台mysql主机
	        $config = $hosts[$randKey];
	        
	        //链接数据库
	        $host = $config['host'];
	        $username = $config['username'];
	        $password = $config['password'];
	        $port = $config['port'];
	        $charset = $config['charset'];

	        $connect = $this->newConnect($host, $username, $password, $this->_database, $port, $charset);
   
			//连接错误时报错信息会是乱码
            if ($connect !== false) {
                $GLOBALS['DB_LINKS'][$this->connectKey] = $connect;
                $isConnected = TRUE;
            } else {
            	$connectError[$host] = $this->connectError;
            	$isConnected = FALSE;
            	$connectCounter--;
            }
        }

        if ($isConnected) {
        	return $this;
        } else {
			$this->error('数据库连接失败: '. json_encode($connectError));
			return $this;
        }
	}

	/**
	 * 查询封装
	 * @param string $sql
	 * @return $this
     * @throws Exception
	 */
	public function query($sql='')
	{
		if (!empty($sql)) {
		    $this->custom = TRUE;
			self::$sqls[] = self::$currentSql = $sql;
		} else {
            self::$sqls[] = self::$currentSql;
		}

		//$sql = strtolower(self::$currentSql);
		$sql = ltrim(self::$currentSql);
		
		if (strlen($sql) == 0) {
		    $this->error('待执行的SQL语句为空');
        }
		
        if (strpos($sql, 'SELECT') === 0 || strpos($sql, 'SHOW') === 0) {
            $this->getConnect('read');//读库
        } else {
            $this->getConnect('write');//写库
        }

        $connectKey = $this->connectKey;
        
		$this->clearQueryParam(); //清除查询条件
        
		//执行查询语句
		$this->rs = $GLOBALS['DB_LINKS'][$connectKey]->query(self::$currentSql);
		
		($this->rs === FALSE) && $this->error($GLOBALS['DB_LINKS'][$connectKey]->error);

		if (strpos($sql, 'REPLACE') === 0) {
			$this->affectRows = $GLOBALS['DB_LINKS'][$connectKey]->affected_rows;

		} elseif (strpos($sql, 'INSERT') === 0) {
		    //返回自增列的值, 主键非自增则返回0
			$this->insertId = $GLOBALS['DB_LINKS'][$connectKey]->insert_id;

		} elseif (strpos($sql, 'DELETE') === 0) {
			$this->affectRows = $GLOBALS['DB_LINKS'][$connectKey]->affected_rows;

		} elseif (strpos($sql, 'UPDATE') === 0) {
			$this->affectRows = $GLOBALS['DB_LINKS'][$connectKey]->affected_rows;
			
		}

		//查询完成后释放链接, 并删除链接对象
		if ($this->isRelease) {
            $GLOBALS['DB_LINKS'][$connectKey]->close();
			unset($GLOBALS['DB_LINKS'][$connectKey]);
		}

		return $this;
	}

	//将结果集转换成数组, 一个一个返回, 如果本函数的返回值会被foreach, 就用此函数
	//如果field不为空，则返回的数组以$field为键重新索引
	public function rsToArrayYield($field = '')
	{
		if ($field) {
			while ($row = $this->rs->fetch_assoc()) {
				$tmp = [];
				$tmp[$row[$field]] = $row;
				yield $tmp;
			}
		} else {
			while ($row = $this->rs->fetch_assoc()) {
				yield $row; //不一次性获取全部数组到内存, 用一个取一个, 返回值的数据类型为"生成器"
			}
		}
	}

	//一次性获取所有数据到内存
	//如果field不为空，则返回的数组以$field为键重新索引
	public function getAll($field='')
	{
		if (empty($field)) {
			$this->data = $this->rs->fetch_all(MYSQLI_ASSOC); //该函数只能用于php的mysqlnd驱动
			
		} else {
			while ($row = $this->rs->fetch_assoc()) {
				$this->data[$row[$field]] = $row;
			}
		}
		return $this->data;
	}

	//获取一条记录
	public function getOne()
	{
		$this->data = $this->rs->fetch_assoc();
		return !empty($this->data) ? $this->data : array();
	}
    
    /**
     * 获取一条记录的某一个字段的值
     * @param string $field 字段名
     * @param string $default 默认值
     * @return string
     * @throws Exception
     */
	public function getOneValue($field, $default='')
	{
        $rs = $this->rs->fetch_assoc();
        
        if (!empty($rs) && !isset($rs[$field])) {
            $this->error('没有发现字段: '.$field);
        }
		return isset($rs[$field]) ? $rs[$field] : $default;
	}

	//获取数据集中所有某个字段的值
	public function getValues($field, $index='')
	{
		$this->getAll();
		if (!empty($index)) {
			return array_column($this->data, $field, $index); //以$index字段的值做索引, 以$field字段的值做值
		} else {
			return array_column($this->data, $field);
		}
	}

	//获取总数
	public function getCount()
	{
        $rs = $this->rs->fetch_assoc();
        return isset($rs['SUMMER_N']) ? $rs['SUMMER_N'] : 0;
	}

    //断开数据库连接
    public function close()
    {
        $GLOBALS['DB_LINKS'][$this->connectKey]->close();
    }
    
    //释放数据
    public function freeResult()
    {
        if ($this->rs instanceof mysqli_result) {
            $this->rs->free_result();
        }
    }

    //事务
    //自动提交开关
    public function autoCommit($bool)
    {
        $GLOBALS['DB_LINKS'][$this->connectKey]->autocommit($bool);
        return $this;
    }
    
    //事务开始
    // http://php.net/manual/zh/mysqli.begin-transaction.php
    public function beginTransaction($flag=MYSQLI_TRANS_START_READ_WRITE, $name)
    {
        $GLOBALS['DB_LINKS'][$this->connectKey]->begin_transaction($flag, $name);
        return $this;
    }

    //事务完成提交
    public function commit()
    {
        $GLOBALS['DB_LINKS'][$this->connectKey]->commit();
        return $this;
    }

    //回滚
    public function rollback()
    {
        $GLOBALS['DB_LINKS'][$this->connectKey]->rollback();
        return $this;
    }

	//获取当前连接
	public static function getCurrentLinks()
	{
		return $GLOBALS['DB_LINKS'];
	}

	//获取所有数据库
    public function getDatabaseList($host)
    {
        DBConfig::init();
        $hostInfo = DBConfig::getHostInfo($host);
        $connect = $this->newConnect($hostInfo['host'], $hostInfo['username'], $hostInfo['password'], '', $hostInfo['port'], $hostInfo['charset']);
        if ($connect !== false) {
            $this->rs = $connect->query('show databases');
            $dbList = array_column($this->rs->fetch_all(), $value=0);
            sort($dbList, SORT_FLAG_CASE);
            return $dbList;
        } else {
            return [];
        }
    }

    //获取所有表
    public function getTableList($host, $dbname)
    {
        DBConfig::init();
        $hostInfo = DBConfig::getHostInfo($host);
        $connect = $this->newConnect($hostInfo['host'], $hostInfo['username'], $hostInfo['password'], $dbname, $hostInfo['port'], $hostInfo['charset']);
        if ($connect !== false) {
            $this->rs = $connect->query('show tables');
            $tableList = array_column($this->rs->fetch_all(), $value=0);
            sort($tableList, SORT_FLAG_CASE);
            return $tableList;
        } else {
            return [];
        }
    }

    public function getFieldList($host, $dbname, $table)
    {
        DBConfig::init();
        $hostInfo = DBConfig::getHostInfo($host);
        $connect = $this->newConnect($hostInfo['host'], $hostInfo['username'], $hostInfo['password'], $dbname, $hostInfo['port'], $hostInfo['charset']);
        if ($connect !== false) {
            $this->rs = $connect->query("select COLUMN_NAME, COLUMN_TYPE, COLUMN_DEFAULT, CHARACTER_SET_NAME,COLUMN_KEY, COLUMN_COMMENT from information_schema.columns where table_schema ='{$dbname}' and table_name = '{$table}'");
            $fieldList = $this->rs->fetch_all(MYSQLI_ASSOC);
            sort($fieldList, SORT_FLAG_CASE);
            return $fieldList;

        } else {
            return [];
        }
    }

    /**
     * 查询语句
     * @param bool $isQuery 组装完sql语句是否立即查询
     * @return $this
     */
    public function select($isQuery=TRUE)
    {
        $where = $this->getWhere();
        $order = $this->getOrder();

        self::$currentSql =  "SELECT {$this->fields} FROM {$this->_dt} {$where} {$this->groupBy} {$this->having} {$order} {$this->limit} {$this->extra}";

        $isQuery && $this->query();
        return $this;
    }

    /**
     * 增, 另注: 主从切换时注意读写权限
     * @param $arrData
     * @param bool $isQuery 组装完sql语句是否立即查询
     * @return $this
     */
    public function insert($arrData, $isQuery=TRUE)
    {
        $arrData = $this->safe($arrData);
        $fields = array_keys($arrData);

        $strFields = implode(',', $fields);
        $strValues = implode(',', $arrData);
        self::$currentSql = "INSERT INTO {$this->_dt} ($strFields) VALUES ($strValues)";

        $isQuery && $this->query();
        return $this;
    }

    /**
     * 增, 注意高并发下不要用 replace into 效率低而且容易死锁
     * @param $arrData
     * @param bool $isQuery 组装完sql语句是否立即查询
     * @return $this
     */
    public function replace($arrData, $isQuery=TRUE)
    {
        $arrData = $this->safe($arrData);
        $fields = array_keys($arrData);

        $strFields = implode(',', $fields);
        $strValues = implode(',', $arrData);
        self::$currentSql = "REPLACE INTO {$this->_dt} ($strFields) VALUES ($strValues)";

        $isQuery && $this->query();
        return $this;
    }

    /**
     * 每次插入多条记录
     * 每条记录的字段相同,但是值不一样
     * @param $strFields
     * @param $arrData
     * @param bool $isQuery 组装完sql语句是否立即查询
     * @return $this
     */
    public function insertm($strFields, $arrData, $isQuery=TRUE)
    {
        $data = [];
        foreach ($arrData as $arr) {
            $arr = $this->safe($arr); //单个SQL语句的数据
            $str = implode(',', $arr);
            $data[] = "($str)";
        }

        $strData = implode(',', $data);

        self::$currentSql = "INSERT INTO {$this->_dt} ($strFields) VALUES {$strData}";

        $isQuery && $this->query();
        return $this;
    }

    /**
     * 删除
     * @param bool $isQuery 组装完sql语句是否立即查询
     * @return $this
     * @throws Exception
     */
    public function delete($isQuery=TRUE)
    {
        $where = $this->getWhere();
        if (empty($where)) {
            $this->error(__METHOD__.'删除时where条件不能为空!'.$this->_dt);
        }

        self::$currentSql = "DELETE FROM {$this->_dt} {$where} {$this->limit}";

        $isQuery && $this->query();
        return $this;
    }

    //改, 自定义update set语句, 注意没有安全校验
    public function addUpdate($str)
    {
        $this->arrUpdate[] = $str;
        return $this;
    }

    //改, 键值对: a = 1, a = 'b', 有安全校验
    public function updateVal($arrData)
    {
        foreach ($arrData as $field => $v) {
            $v = $this->safe($v, $field);
            $this->arrUpdate[] = "{$field} = {$v}";
        }
        return $this;
    }

    //改, 自定义运算符
    // updateOp(a, b, + 1) ==> a = b + 1;
    public function updateOp($field1, $field2, $op, $numeric)
    {
        if (!is_numeric($numeric)) {
            $this->error(__METHOD__." {$field1} = {$field2} {$op} {$numeric} 中值不是数字");
        } else {
            $this->arrUpdate[] = "{$field1} = {$field2} {$op} {$numeric}";
        }

        return $this;
    }

    /**
     * 改, 组装update语句
     * @param bool $isQuery  组装完sql语句是否立即查询
     * @return $this
     * @throws Exception
     */
    public function update($isQuery=TRUE)
    {
        $where = $this->getWhere();
        if (empty($where)) {
            $this->error(__METHOD__.'更新时where条件不能为空!'.self::$currentSql);
        }

        $strSql = implode(',', $this->arrUpdate);

        self::$currentSql = "UPDATE {$this->_dt} set {$strSql} {$where} {$this->limit}";

        $isQuery && $this->query();
        return $this;
    }

    /**
     * 获取总数
     * @param bool $isQuery  组装完sql语句是否立即查询
     * @return $this|string
     */
    public function count($isQuery=TRUE)
    {
        $where = $this->getWhere();
        self::$currentSql = "SELECT COUNT(1) AS SUMMER_N FROM {$this->_dt} {$where}";

        if ($isQuery) {
            $this->query();
            return $this->getCount();
        } else {
            return $this;
        }
    }

    //添加自定义where条件, 注意直接调用时没有安全校验
    public function addWhere($str)
    {
        $this->arrWhere[] = $str;
        return $this;
    }

    //where 等于
    public function where($arrData)
    {
        if (empty($arrData)) {
            return $this;
        }

        $arrData = $this->safe($arrData);

        foreach ($arrData as $k => $v) {
            $this->addWhere("({$k} = {$v})");
        }

        return $this;
    }

    //where in
    public function whereIn($key, $arrData, $force=false)
    {
        if (empty($arrData)) {
            if ($force === false) {
                $str = "({$key} IN (''))";
                $this->addWhere($str);
                return $this;
            } else {
                $this->error('where in 条件为空');
            }
        }

        $this->safe($arrData);

        $arrData = array_unique($arrData);

//		sort($arrData);

        foreach ($arrData as $k => $v) {
            if (!is_numeric($v) && !is_string($v)) {
                $this->error(__METHOD__."第 {$k} 个值的数据类型不对!");
                unset($arrData[$k]);
            } else {
                $arrData[$k] = !is_string($v) ? $v : "'{$v}'";
            }
        }

        $strData = implode(',', $arrData);

        $this->addWhere("({$key} IN ( {$strData} ))");

        return $this;
    }

    //between and
    public function whereBetween($key, $min, $max)
    {
        $min = $this->safe($min);
        $max = $this->safe($max);

        $str = "({$key} BETWEEN {$min} AND {$max})";
        $this->addWhere($str);
        return $this;
    }

    //where(a, >, b) ==> where a>b
    public function whereOp($key, $op, $value)
    {
        $value = $this->safe($value, $key);
        $this->addWhere("({$key} {$op} {$value})");
        return $this;
    }

    //获取最终查询用的where条件
    public function getWhere()
    {
        if (!empty($this->arrWhere)) {
            return 'WHERE '.implode(' AND ', $this->arrWhere);
        } else {
            return '';
        }
    }

    //以逗号隔开
    public function fields($fields)
    {
        $this->fields = $fields;
        return $this;
    }

    // order by a desc
    public function order($order)
    {
        $this->arrOrder[] = $order;
        return $this;
    }

    //获取order语句
    public function getOrder()
    {
        if (empty($this->arrOrder)) {
            return '';
        } else {
            $str = implode(',', $this->arrOrder);
            $this->order = "ORDER BY {$str}";
        }
        return $this->order;
    }

    //group by year, month
    public function groupBy($str)
    {
        $this->groupBy = "GROUP BY {$str}";
        return $this;
    }

    // haveing count(*) > 1 and t1.status = 1
    public function having($str)
    {
        $this->having = "HAVING {$str} ";
    }

    //e.g. '0, 10'
    //用limit的时候可以加where条件优化：select ... where id > 1234 limit 0, 10
    public function limit($limit)
    {
        $this->limit = 'LIMIT '.$limit;
        return $this;
    }

    /**
     * 组装最终的join 语句
     * @param bool $isQuery 组装完sql语句是否立即查询
     * @return $this
     */
    public function join($isQuery=TRUE)
    {
        $where = $this->getWhere();
        $order = $this->getOrder();
        $joinFields = $this->getJoinFields();
        $joinTable = $this->getJoinTable();

        self::$currentSql = "SELECT {$joinFields} FROM {$this->_dt} {$joinTable} {$where} {$this->groupBy} {$this->having} {$order} {$this->limit}";

        $isQuery && $this->query();
        return $this;
    }

    /**
     * 连接查询, 设置查询字段
     * 可多次调用
     * @param string $table 表名
     * @param string $fields 该表的字段
     * @return $this
     */
    public function joinFields($table, $fields)
    {
        $fields = preg_replace('#,\s+#', ',', $fields);//去掉空白
        $fields = explode(',', $fields);
        foreach ($fields as $k => $v) {
            $fields[$k] = $table.'.'.$v;
        }
        $this->joinField[] = implode(',', $fields);

        return $this;
    }

    /**
     * 组装要查询的字段
     * @return string
     */
    public function getJoinFields()
    {
        return implode(', ', $this->joinField);
    }

    /**
     * 组装 left join .. on ..
     * 注意, where中的[not] exists 语句中的不能有 inner join tb on xxx and xxx 中的 and xxx
     * @param string $table1 左表的model名, 会根据它来查找真正的表名
     * @param string $field1 关联字段
     * @param string $table2 右表的model名, 会根据它来查找真正的表名
     * @param string $field2 关联字段
     * @param string $joinMethod join方式, 默认LEFT, 还可以是 RIGHT, INNER
     * @return $this
     * @throws Exception
     */
    public function joinTable($table1, $field1, $table2, $field2, $joinMethod = 'LEFT')
    {
        $arr1 = self::getDBInfo($table1);
        $table1Name = $arr1['table'];

        $arr2 = self::getDBInfo($table2);
        $table2Name = $arr2['table'];

        $str = $joinMethod. " JOIN {$table2Name} ON {$table1Name}.{$field1} = {$table2Name}.{$field2}";
        $this->joinTable[] = $str;

        return $this;
    }

    //将数组转换成字符串
    public function getJoinTable()
    {
        return implode(' ', $this->joinTable);
    }

    //tf1: table1.field1
    public function joinTables($tf1, $tf2, $joinMethod = 'LEFT')
    {
        list($table1, $field1) = explode('.', $tf1);
        list($table2, $field2) = explode('.', $tf2);
        $arr1 = self::getDBInfo($table1);
        $table1Name = $arr1['table'];

        $arr2 = self::getDBInfo($table2);
        $table2Name = $arr2['table'];

        $joinAnd = $this->getJoinAnd();

        $str = $joinMethod. " JOIN {$table2Name} ON {$table1Name}.{$field1} = {$table2Name}.{$field2} {$joinAnd}";

        $this->joinAnd = array();
        $this->joinTable[] = $str;

        return $this;
    }

    public function joinAnd($tf, $operation, $value)
    {
        list($table, $field) = explode('.', $tf);
        $arr = self::getDBInfo($table);
        $tableName = $arr['table'];

        if (is_int($value)) {
            $this->joinAnd[] = "{$tableName}.{$field} {$operation} {$value}"; //table.field = 123 或者 table.field > 123
        } else {
            if (strpos($value, '(') !== FALSE) {
                $this->joinAnd[] = "{$tableName}.{$field} {$operation} {$value}"; // table.field in (xxx)
            } elseif (stripos($value, 'and') !== FALSE) {
                $this->joinAnd[] = "({$tableName}.{$field} {$operation} {$value})"; // table.field between xxx and yyy
            } else {
                $this->joinAnd[] = "{$tableName}.{$field} {$operation} '{$value}'"; //table.field = 'xxx'
            }
        }

        return $this;
    }

    public function getJoinAnd()
    {
        if (!empty($this->joinAnd)) {
            return ' AND '. implode(' AND ', $this->joinAnd);
        } else {
            return '';
        }
    }

    //自定义sql后缀,
    // setExtra(for update) ==> select.... for update;
    // setExtra(lock in share mode) ==> select.... lock in share mode;
    public function setExtra($str)
    {
        $this->extra = $str;
    }

    //清除查询数据, 避免影响下一次查询
    public function clearQueryParam()
    {
        $this->arrWhere = [];
        $this->arrUpdate = [];
        $this->order = '';
        $this->arrOrder = [];
        $this->limit = '';
        $this->groupBy = '';
        $this->having = '';
        $this->joinField = [];
        $this->joinTable = [];
        $this->joinOn = [];
    }

    /**
     * @param string $modelName 对应$TableInfo的键名
     * @return array
     * @throws Exception
     */
    public static function getDBInfo($modelName)
    {
        $TableInfo = DBConfig::$TableInfo;
        $strDT = '';
        if (array_key_exists($modelName, $TableInfo)) {
            $strDT = $TableInfo[$modelName];//获得database table 字符串
        } else {
            foreach ($TableInfo as $pattern => $dt) {
                if (strpos($pattern, '(') !== FALSE) {
                    preg_match('#' . $pattern . '#', $modelName, $matches);

                    if (!empty($matches)) {
                        $strDT = $dt;
                        foreach ($matches as $key => $value) {
                            $strDT = str_replace('$' . $key, $value, $strDT);
                        }
                        break;
                    }
                }
            }
        }

        if (!empty($strDT)) {
            $strDT = preg_replace('#,\s+#', ',', $strDT);//去掉空白
            $arr   = explode(',', $strDT);
            return array(
                'vhost' => $arr[0],
                'database' => $arr[1],
                'table' => $arr[2],
            );
        } else {
            throw new Exception("未找到数据表 {$modelName} !");
        }
    }

    public function safe($data, $field='')
    {
        if (is_string($data) && !in_array($data, $this->raw)) {
            $data = $this->excapeString($data);
            return "'{$data}'";

        } elseif (is_numeric($data) || in_array($data, $this->raw)) {
            return $data;

        } elseif (is_array($data)) {
            foreach ($data as $k => $v) {
                if (is_string($v) && !in_array($v, $this->raw)) {
                    $v = $this->excapeString($v);;
                    $data[$k] = "'{$v}'";
                } elseif (is_numeric($v) || in_array($v, $this->raw)) {
                    $data[$k] = $v;
                } else {
                    $this->error(__METHOD__." 字段 $k 数据类型不支持");
                }
            }
            return $data;
        } else {
            $this->error(__METHOD__." 字段 $field 数据类型不支持");
        }
    }

    /**
      * Returns a string with backslashes before characters that need to be escaped.
      * As required by MySQL and suitable for multi-byte character sets
      * Characters encoded are NUL (ASCII 0), \n, \r, \, ', ", and ctrl-Z.
      * In addition, the special control characters % and _ are also escaped,
      * suitable for all statements, but especially suitable for `LIKE`.
      *
      * @param string $string String to add slashes to
      * @return $string with `\` prepended to reserved characters
      *
      * @author Trevor Herselman
      */
    public function excapeString($string)
    {
        if (function_exists('mb_ereg_replace')) {
            return mb_ereg_replace('[\x00\x0A\x0D\x1A\x22\x25\x27\x5C\x5F]', '\\\0', $string);
        } else {
            return preg_replace('/[\x00\x0A\x0D\x1A\x22\x25\x27\x5C\x5F]/u', '\\\$0', $string);
        }

    }

	public function error($str)
    {
        throw new Exception($str.'==sql=='. self::$currentSql.'==message=='.json_encode($this->message));
    }
}